from datetime import datetime, timedelta

from google.appengine.ext import webapp

from google.appengine.ext import db
from google.appengine.api import memcache

import pickle
import Cookie
import sys
import os

class Session(db.Model):
    """
    A google appengine datastore based session store. 
    """
    
    created = db.DateTimeProperty(auto_now_add=True)
    last_access = db.DateTimeProperty(auto_now=True)
    
    enabled = False
    memcache_namespace = 'session'
    cache = None
    
    cookie_key_prefix = 'session_key'
    version = os.environ.get('CURRENT_VERSION_ID','0')
    
    keep_alive = timedelta(hours=5)
    
    def __init__(self, *args, **kwargs):
        if self.enabled:
            self.cache = memcache.Client()
        super(Session, self).__init__(*args, **kwargs)
        
    @staticmethod
    def get_cookie_key():
        return '_'.join([Session.cookie_key_prefix,Session.version,str(hash(Session))]);        
        
    @staticmethod
    def load(session_key):
        store = None
        if session_key:
            store = Session.get(session_key)
            if store and store.has_expired():
                store.delete()
                store = None
        if not store:
            store = Session.get(Session().put())
        else:
            store.put()
        return store
    def __getsessionquery(self, name):
        return self._values.filter("name = ",name)
    
    def __buildcachekey(self, name):
        return str(self.key())+"_"+name
            
    def __getitem__(self, name):
        value = None        
        if self.cache:
            value = self.cache.get(self.__buildcachekey(name), self.memcache_namespace)            
        if not value:
            session = self.__getsessionquery(name).get()
            if session:
                value = pickle.loads(session.value)
            if value and self.cache:
                self.cache.set(self.__buildcachekey(name), value, namespace=self.memcache_namespace)
        return value
        
    def __setitem__(self, name, value):
        if self.cache:
            self.cache.set(self.__buildcachekey(name), value, namespace=self.memcache_namespace)
        session = self.__getsessionquery(name).get()       
        value = pickle.dumps(value)
        if not session:
            session = SessionValue(parent=self,session=self,name=name)
        session.value = value
        
        session.put()
        
    @staticmethod
    def cleanup(self):
        limit = datetime.now() - Session.keep_alive
        for store in Session.all().filter("last_access < ",limit).fetch(10):
            store.delete()
            
    def expires(self):
        return self.last_access+Session.keep_alive
    
    def has_expired(self):
        return self.expires()<datetime.now()
        
    def delete(self):
        self._values.run().delete()
        super(Session,self).delete(self)
        
    def exists(self, name):
        return self.__getsessionquery().count()>0    
    
        

class SessionValue(db.Model):
    session = db.ReferenceProperty(Session,required=True,collection_name='_values')
    name = db.StringProperty(required=True)
    value = db.BlobProperty()

class StatefulHandler(webapp.RequestHandler):    
    
    def initialize(self,request, response):        
        webapp.RequestHandler.initialize(self, request, response)
        cookie_key = Session.get_cookie_key()   
        session_key = request.cookies.get(cookie_key, default=None)            
        request.session = response.session = Session.load(session_key)        
        cookie = Cookie.SimpleCookie() 
        cookie[cookie_key]=response.session.key()
        cookie[cookie_key]['expires']=Session.keep_alive.seconds
        cookie[cookie_key]['path']='/'                
        response.headers.add_header('Set-Cookie', cookie[cookie_key].OutputString())
    
            
    def __init__(self, *args, **kwargs):
        super(StatefulHandler, self).__init__(*args, **kwargs)
        
    @staticmethod
    def current():
        i = 0
        limit = sys.getrecursionlimit()
        while i<limit:
            try:
                frame = sys._getframe(i)
            except ValueError:
                return None
            if frame.f_locals.has_key('self'):
                self = frame.f_locals['self']
                if isinstance(self,webapp.RequestHandler) and  hasattr(self, 'request'):
                    return self
            i+=1
